package com.github.shuaidd.rest.runtime;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.Page;
import com.github.shuaidd.model.execption.BadRequestException;
import com.github.shuaidd.service.runtime.LwfPermissionService;
import com.github.shuaidd.service.runtime.LwfRelatedContentService;
import org.activiti.app.domain.runtime.RelatedContent;
import org.activiti.app.model.component.SimpleContentTypeMapper;
import org.activiti.app.model.runtime.CreateProcessInstanceRepresentation;
import org.activiti.app.model.runtime.ProcessInstanceRepresentation;
import org.activiti.app.model.runtime.RelatedContentRepresentation;
import org.activiti.app.service.api.UserCache;
import org.activiti.app.service.runtime.ActivitiService;
import org.activiti.app.service.runtime.PermissionService;
import org.activiti.app.service.runtime.RelatedContentService;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.StartEvent;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.identity.User;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.form.api.FormRepositoryService;
import org.activiti.form.api.FormService;
import org.activiti.form.model.FormDefinition;
import org.activiti.form.model.FormField;
import org.activiti.form.model.FormFieldTypes;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class AbstractProcessInstancesResource {

    @Autowired
    protected ActivitiService activitiService;

    @Autowired
    protected RepositoryService repositoryService;

    @Autowired
    protected HistoryService historyService;

    @Autowired
    protected FormRepositoryService formRepositoryService;

    @Autowired
    protected FormService formService;

    @Autowired
    protected LwfPermissionService permissionService;

    @Autowired
    protected LwfRelatedContentService relatedContentService;

    @Autowired
    protected SimpleContentTypeMapper typeMapper;

    @Autowired
    protected UserCache userCache;

    @Autowired
    protected ObjectMapper objectMapper;

    public ProcessInstanceRepresentation startNewProcessInstance(CreateProcessInstanceRepresentation startRequest) {
        if (StringUtils.isEmpty(startRequest.getProcessDefinitionId())) {
            throw new BadRequestException("Process definition id is required");
        }

        FormDefinition formDefinition = null;
        Map<String, Object> variables = null;

        ProcessDefinition processDefinition = permissionService.getProcessDefinitionById(startRequest.getProcessDefinitionId());

        if (startRequest.getValues() != null || startRequest.getOutcome() != null) {
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
            Process process = bpmnModel.getProcessById(processDefinition.getKey());
            FlowElement startElement = process.getInitialFlowElement();
            if (startElement instanceof StartEvent) {
                StartEvent startEvent = (StartEvent) startElement;
                if (StringUtils.isNotEmpty(startEvent.getFormKey())) {
                    formDefinition = formRepositoryService.getFormDefinitionByKey(startEvent.getFormKey());
                    if (formDefinition != null) {
                        variables = formService.getVariablesFromFormSubmission(formDefinition, startRequest.getValues(), startRequest.getOutcome());
                    }
                }
            }
        }

        ProcessInstance processInstance = activitiService.startProcessInstance(startRequest.getProcessDefinitionId(), variables, startRequest.getName());

        // Mark any content created as part of the form-submission connected to the process instance and field
        if (formDefinition != null) {
            for (FormField formField : formDefinition.getFields()) {
                if (FormFieldTypes.UPLOAD.equals(formField.getType())) {
                    String variableName = formField.getId();
                    if (variables.containsKey(variableName)) {
                        String variableValue = (String) variables.get(variableName);
                        if (StringUtils.isNotEmpty(variableValue)) {
                            String[] relatedContentIds = StringUtils.split(variableValue, ",");
                            for (String id : relatedContentIds) {
                                relatedContentService.setContentField(Long.parseLong(id), formField.getId(), processInstance.getId(), null);
                            }
                        }
                    }
                }
            }
        }

        HistoricProcessInstance historicProcess = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstance.getId()).singleResult();

        if (formDefinition != null) {
            formService.storeSubmittedForm(variables, formDefinition, null, historicProcess.getId());
        }

        User user = null;
        if (historicProcess.getStartUserId() != null) {
            UserCache.CachedUser cachedUser = userCache.getUser(historicProcess.getStartUserId());
            if (cachedUser != null && cachedUser.getUser() != null) {
                user = cachedUser.getUser();
            }
        }
        return new ProcessInstanceRepresentation(historicProcess, processDefinition, ((ProcessDefinitionEntity) processDefinition).isGraphicalNotationDefined(), user);

    }

    protected Map<String, List<RelatedContent>> groupContentByField(Page<RelatedContent> allContent) {
        HashMap<String, List<RelatedContent>> result = new HashMap<String, List<RelatedContent>>();
        List<RelatedContent> list;
        for (RelatedContent content : allContent.getResult()) {
            list = result.computeIfAbsent(content.getField(), k -> new ArrayList<RelatedContent>());
            list.add(content);
        }
        return result;
    }

    protected RelatedContentRepresentation createRelatedContentResponse(RelatedContent relatedContent) {
        return new RelatedContentRepresentation(relatedContent, typeMapper);
    }
}
